﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Freedom.Common.Collections;
using log4net;

namespace Freedom.WinCommon.Collections
{
    /// <summary>
    /// AsyncObjectCollection
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class AsyncObjectCollection<T> : ObjectCollection<T>, ILoadableObject
        where T : class, ICopiable<T>, IIdentifiable
    {
        #region Properties

        private bool _isLoading;
        readonly ObjectCollector<T> _loadedItems = new ObjectCollector<T>();

        private readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Gets a value indicating whether this instance is loaded.
        /// </summary>
        /// <value><c>true</c> if this instance is loaded; otherwise, <c>false</c>.</value>
        public bool IsLoaded
        {
            get { return !_isLoading; }
        }

        /// <summary>
        /// Gets the size of the package.
        /// </summary>
        /// <value>The size of the package.</value>
        protected virtual int PackageSize
        {
            get { return 40; }
        }

        private static readonly IEqualityComparer<T> _comparer = new IdentifiableComparer<T>();
        /// <summary>
        /// Gets the comparer.
        /// </summary>
        /// <value>The comparer.</value>
        protected virtual IEqualityComparer<T> Comparer
        {
            get { return _comparer; }
        }

        /// <summary>
        /// Gets the state of the loading.
        /// </summary>
        /// <value>The get loaded items count.</value>
        /// <returns></returns>
        int? ILoadableObject.LoadedItemsCount
        {
            get { return _isLoading ? Items.Count : (int?)null; }
        }

        #endregion

        /// <summary>
        /// Updates the state.
        /// </summary>
        public void UpdateState()
        {
            if(_loadedItems.IsEmpty) return;
            
            AddRange(_loadedItems.ExctractAll().Except(Items, Comparer).ToArray());
        }

        /// <summary>
        /// Loads the items.
        /// </summary>
        /// <param name="fromConstructor">if set to <c>true</c> [from constructor].</param>
        protected override void LoadItems(bool fromConstructor)
        {
            if (_isLoading) return;
            Clear();
            ((MethodInvoker)AsyncLoad).BeginInvoke(null, null);
        }

        /// <summary>
        /// Finds the by id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        protected T FindById(object id)
        {
            int index;
            return Find(e => e.Id.Equals(id), out index);
        }

        private void AsyncLoad()
        {
            _isLoading = true;
            try
            {
                var skip = 0;
                do
                {

                    var items = ReadPage(skip, PackageSize);
                    if(items!=null)
                        _loadedItems.Put(items);

                    skip += PackageSize;
                    _isLoading = DetermineContinueLoading(items);
                }
                while (_isLoading);
            }
            catch (Exception ex)
            {
                _log.Error(ex);
            }
            finally
            {
                _isLoading = false;
            }
        }

        /// <summary>
        /// Reads the page.
        /// </summary>
        /// <param name="startIndex">The start index.</param>
        /// <param name="count">The count.</param>
        protected abstract T[] ReadPage(int startIndex, int count);

        /// <summary>
        /// Determines the continue loading.
        /// </summary>
        /// <param name="readedItems">The readed items.</param>
        /// <returns>True-need load next chunk</returns>
        protected virtual bool DetermineContinueLoading(T[] readedItems)
        {
            if (readedItems == null)
                return false;
            return readedItems.Length == PackageSize;
        }
    }
}
